/*-------------------------------------------------------------------------
 *
 * basebackup_server.c
 *	  存储在服务器上的基础备份归档
 *
 * IDENTIFICATION
 *	  src/backend/backup/basebackup_server.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/xact.h"
#include "backup/basebackup.h"
#include "backup/basebackup_sink.h"
#include "catalog/pg_authid.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "utils/acl.h"
#include "utils/timestamp.h"
#include "utils/wait_event.h"

typedef struct bbsink_server
{
	/* 所有类型的接收器的通用信息。 */
	bbsink		base;

	/* 备份存储的目录。 */
	char	   *pathname;

	/* 当前打开的文件（如果没有打开，则为0）。 */
	File		file;

	/* 当前文件位置。 */
	off_t		filepos;
} bbsink_server;

static void fc_bbsink_server_begin_archive(bbsink *fc_sink,
										const char *fc_archive_name);
static void fc_bbsink_server_archive_contents(bbsink *fc_sink, size_t fc_len);
static void fc_bbsink_server_end_archive(bbsink *fc_sink);
static void fc_bbsink_server_begin_manifest(bbsink *fc_sink);
static void fc_bbsink_server_manifest_contents(bbsink *fc_sink, size_t fc_len);
static void fc_bbsink_server_end_manifest(bbsink *fc_sink);

static const bbsink_ops bbsink_server_ops = {
	.begin_backup = bbsink_forward_begin_backup,
	.begin_archive = fc_bbsink_server_begin_archive,
	.archive_contents = fc_bbsink_server_archive_contents,
	.end_archive = fc_bbsink_server_end_archive,
	.begin_manifest = fc_bbsink_server_begin_manifest,
	.manifest_contents = fc_bbsink_server_manifest_contents,
	.end_manifest = fc_bbsink_server_end_manifest,
	.end_backup = bbsink_forward_end_backup,
	.cleanup = bbsink_forward_cleanup
};

/*
 * 创建一个新的 'server' bbsink。
 */
bbsink * bbsink_server_new(bbsink *fc_next, char *fc_pathname)
{
	bbsink_server *fc_sink = palloc0(sizeof(bbsink_server));

	*((const bbsink_ops **) &fc_sink->base.bbs_ops) = &bbsink_server_ops;
	fc_sink->pathname = fc_pathname;
	fc_sink->base.bbs_next = fc_next;

	/* 在这种情况下，复制权限不足。 */
	StartTransactionCommand();
	if (!has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser or a role with privileges of the pg_write_server_files role to create backup stored on server")));
	CommitTransactionCommand();

	/*
	 * 将备份存储在与您正在备份的目录中并不是一个好主意。
	 * 如果我们在这里允许相对路径，那么很容易意外发生，因此我们不这样做。用户仍然可以
	 * 通过在路径名中包含 $PGDATA 的绝对路径来实现相同的目的，但这可能是一个故意的错误决定，而不是
	 * 意外。
	 */
	if (!is_absolute_path(fc_pathname))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("relative path not allowed for backup stored on server")));

	switch (pg_check_dir(fc_pathname))
	{
		case 0:

			/*
			 * 不存在，因此使用我们在数据目录本身的新子目录上使用的相同权限来创建它。
			 */
			if (MakePGDirectory(fc_pathname) < 0)
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not create directory \"%s\": %m", fc_pathname)));
			break;

		case 1:
			/* 已存在，空的。 */
			break;

		case 2:
		case 3:
		case 4:
			/* 已存在，不为空。 */
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_FILE),
					 errmsg("directory \"%s\" exists but is not empty",
							fc_pathname)));
			break;

		default:
			/* 访问问题。 */
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not access directory \"%s\": %m",
							fc_pathname)));
	}

	return &fc_sink->base;
}

/*
 * 打开此归档的正确输出文件。
 */
static void fc_bbsink_server_begin_archive(bbsink *fc_sink, const char *fc_archive_name)
{
	bbsink_server *fc_mysink = (bbsink_server *) fc_sink;
	char	   *fc_filename;

	Assert(fc_mysink->file == 0);
	Assert(fc_mysink->filepos == 0);

	fc_filename = psprintf("%s/%s", fc_mysink->pathname, fc_archive_name);

	fc_mysink->file = PathNameOpenFile(fc_filename,
									O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
	if (fc_mysink->file <= 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not create file \"%s\": %m", fc_filename)));

	pfree(fc_filename);

	bbsink_forward_begin_archive(fc_sink, fc_archive_name);
}

/*
 * 将数据写入输出文件。
 */
static void fc_bbsink_server_archive_contents(bbsink *fc_sink, size_t fc_len)
{
	bbsink_server *fc_mysink = (bbsink_server *) fc_sink;
	int			fc_nbytes;

	fc_nbytes = FileWrite(fc_mysink->file, fc_mysink->base.bbs_buffer, fc_len,
					   fc_mysink->filepos, WAIT_EVENT_BASEBACKUP_WRITE);

	if (fc_nbytes != fc_len)
	{
		if (fc_nbytes < 0)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not write file \"%s\": %m",
							FilePathName(fc_mysink->file)),
					 errhint("Check free disk space.")));
		/* 短写：适当投诉 */
		ereport(ERROR,
				(errcode(ERRCODE_DISK_FULL),
				 errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u",
						FilePathName(fc_mysink->file),
						fc_nbytes, (int) fc_len, (unsigned) fc_mysink->filepos),
				 errhint("Check free disk space.")));
	}

	fc_mysink->filepos += fc_nbytes;

	bbsink_forward_archive_contents(fc_sink, fc_len);
}

/*
 * fsync 和关闭当前输出文件。
 */
static void fc_bbsink_server_end_archive(bbsink *fc_sink)
{
	bbsink_server *fc_mysink = (bbsink_server *) fc_sink;

	/*
	 * 我们故意不在这里使用 data_sync_elevel，因为服务器
	 * 不应该因为无法保证备份已写入磁盘而导致 PANIC。
	 * 在这种情况下运行恢复也无济于事。
	 */
	if (FileSync(fc_mysink->file, WAIT_EVENT_BASEBACKUP_SYNC) < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not fsync file \"%s\": %m",
						FilePathName(fc_mysink->file))));


	/* 我们现在完成了这个文件。 */
	FileClose(fc_mysink->file);
	fc_mysink->file = 0;
	fc_mysink->filepos = 0;

	bbsink_forward_end_archive(fc_sink);
}

/*
 * 打开我们将写入清单的输出文件。
 *
 * 就像 pg_basebackup 一样，我们首先以临时名称写入清单，然后在 fsync 后将其重命名到正确的位置。
 * 这样，如果清单在那里且名称正确，用户可以确保备份已完成。
 */
static void fc_bbsink_server_begin_manifest(bbsink *fc_sink)
{
	bbsink_server *fc_mysink = (bbsink_server *) fc_sink;
	char	   *fc_tmp_filename;

	Assert(fc_mysink->file == 0);

	fc_tmp_filename = psprintf("%s/backup_manifest.tmp", fc_mysink->pathname);

	fc_mysink->file = PathNameOpenFile(fc_tmp_filename,
									O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
	if (fc_mysink->file <= 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not create file \"%s\": %m", fc_tmp_filename)));

	pfree(fc_tmp_filename);

	bbsink_forward_begin_manifest(fc_sink);
}

/*
 * 每一块清单数据使用CopyData消息发送。
 */
static void fc_bbsink_server_manifest_contents(bbsink *fc_sink, size_t fc_len)
{
	bbsink_server *fc_mysink = (bbsink_server *) fc_sink;
	int			fc_nbytes;

	fc_nbytes = FileWrite(fc_mysink->file, fc_mysink->base.bbs_buffer, fc_len,
					   fc_mysink->filepos, WAIT_EVENT_BASEBACKUP_WRITE);

	if (fc_nbytes != fc_len)
	{
		if (fc_nbytes < 0)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not write file \"%s\": %m",
							FilePathName(fc_mysink->file)),
					 errhint("Check free disk space.")));
		/* 短写：适当投诉 */
		ereport(ERROR,
				(errcode(ERRCODE_DISK_FULL),
				 errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u",
						FilePathName(fc_mysink->file),
						fc_nbytes, (int) fc_len, (unsigned) fc_mysink->filepos),
				 errhint("Check free disk space.")));
	}

	fc_mysink->filepos += fc_nbytes;

	bbsink_forward_manifest_contents(fc_sink, fc_len);
}

/*
 * fsync 备份清单，关闭文件，然后重命名到正确的位置。
 */
static void fc_bbsink_server_end_manifest(bbsink *fc_sink)
{
	bbsink_server *fc_mysink = (bbsink_server *) fc_sink;
	char	   *fc_tmp_filename;
	char	   *fc_filename;

	/* 我们现在完成了这个文件。 */
	FileClose(fc_mysink->file);
	fc_mysink->file = 0;

	/*
	 * 重命名到正确的位置。这也将 fsync 临时文件，因此我们在这里不需要这样做。
	 * 我们在这里不使用 data_sync_elevel 的原因与 bbsink_server_end_archive 中相同。
	 */
	fc_tmp_filename = psprintf("%s/backup_manifest.tmp", fc_mysink->pathname);
	fc_filename = psprintf("%s/backup_manifest", fc_mysink->pathname);
	durable_rename(fc_tmp_filename, fc_filename, ERROR);
	pfree(fc_filename);
	pfree(fc_tmp_filename);

	bbsink_forward_end_manifest(fc_sink);
}
